package club.xyes.zkh.ecommerce.web.api.controller;

import club.xyes.zkh.ecommerce.commons.dto.MinimalUserInfo;
import club.xyes.zkh.ecommerce.commons.dto.Tuple2;
import club.xyes.zkh.ecommerce.commons.entity.*;
import club.xyes.zkh.ecommerce.commons.exception.BadRequestException;
import club.xyes.zkh.ecommerce.commons.exception.DiscountException;
import club.xyes.zkh.ecommerce.commons.vo.GeneralResult;
import club.xyes.zkh.ecommerce.commons.vo.payment.PrePayResultVo;
import club.xyes.zkh.ecommerce.service.*;
import club.xyes.zkh.ecommerce.service.listener.OnPaymentSuccessListener;
import club.xyes.zkh.ecommerce.web.controller.AbstractControllerSupport;
import club.xyes.zkh.ecommerce.wechat.dto.SaoBeiNotifyVo;
import club.xyes.zkh.ecommerce.wechat.dto.SaoBeiPayResult;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Create by 郭文梁 2019/4/19 0019 13:31
 * PaymentController
 * 支付相关控制器
 *
 * @author 郭文梁
 * @data 2019/4/19 0019
 */
@RestController
@RequestMapping("/api/pay")
@Api("/支付相关API")
@Slf4j
public class PaymentController extends AbstractControllerSupport {
    private final TransactionLogService transactionLogService;
    private final UserService userService;
    private final ShopService shopService;
    private final UserMembershipService userMembershipService;
    private final UserCouponService userCouponService;
    private final UserAccessShopLogService userAccessShopLogService;
    private final OnPaymentSuccessListener onPaymentSuccessListener;

    public PaymentController(TransactionLogService transactionLogService,
                             UserService userService,
                             ShopService shopService,
                             UserMembershipService userMembershipService,
                             UserCouponService userCouponService,
                             UserAccessShopLogService userAccessShopLogService,
                             OnPaymentSuccessListener onPaymentSuccessListener) {
        this.transactionLogService = transactionLogService;
        this.userService = userService;
        this.shopService = shopService;
        this.userMembershipService = userMembershipService;
        this.userCouponService = userCouponService;
        this.userAccessShopLogService = userAccessShopLogService;
        this.onPaymentSuccessListener = onPaymentSuccessListener;
    }

    /**
     * 发起支付
     *
     * @param fee 支付金额
     * @return GeneralResult
     */
    @ApiOperation("发起支付")
    @PostMapping("/_pay")
    public GeneralResult<PrePayResultVo<SaoBeiPayResult>> pay(@RequestParam("fee") int fee,
                                                              @RequestParam("shopId") Integer shopId) {
        MinimalUserInfo userInfo = getUserInfo();
        assert userInfo != null;
        //获取用户信息
        User user = userService.getRequired(userInfo.getUserId());
        //获取商铺信息
        Shop shop = shopService.getRequired(shopId);
        try {
            PrePayResultVo<SaoBeiPayResult> res = transactionLogService.prePay(user, shop, fee);
            userAccessShopLogService.log(user, shop);
            return GeneralResult.ok(res);
        } catch (Exception e) {
            e.printStackTrace();
            return GeneralResult.error(e.getMessage());
        }
    }

    /**
     * 优惠支付
     *
     * @param totalFee         支付金额 必填
     * @param shopId           商铺ID 必填
     * @param userCouponId     使用的优惠券ID
     * @param noDiscountAmount 不参与优惠金额
     * @param userMembershipId 使用的会员优惠ID
     * @return GR
     */
    @ApiOperation("优惠支付")
    @PostMapping("/preferential-pay")
    public GeneralResult<?> preferentialPay(@RequestParam("totalFee") Integer totalFee,
                                            @RequestParam("shopId") Integer shopId,
                                            @RequestParam("noDiscountAmount") Integer noDiscountAmount,
                                            Integer userCouponId,
                                            Integer userMembershipId) {
        int fee = totalFee - noDiscountAmount;
        @NotNull MinimalUserInfo userInfo = getRequiredUserInfo();
        User user = userService.getRequired(userInfo.getUserId());
        Shop shop = shopService.getRequired(shopId);
        int discount = 0;

        //计算优惠券优惠
        Tuple2<UserCoupon, Integer> couponDiscount;
        try {
            couponDiscount = userCouponService.getDiscount(userCouponId, user, shop, fee);
        } catch (DiscountException e) {
            return GeneralResult.badRequest(e.getMessage());
        }
        boolean useCouponDiscount = couponDiscount.getO1() != null;
        log.debug("Discount by coupon {}, use coupon discount {}", couponDiscount, useCouponDiscount);
        if (useCouponDiscount) {
            discount += couponDiscount.getO2();
        }

        //计算会员优惠
        Tuple2<UserMembership, Integer> membershipDiscount;
        try {
            membershipDiscount = userMembershipService.getDiscount(userMembershipId, user, shop, useCouponDiscount ? fee - couponDiscount.getO2() : fee);
        } catch (DiscountException e) {
            return GeneralResult.badRequest(e.getMessage());
        }
        //是否使用会员优惠
        boolean useMembershipDiscount = membershipDiscount.getO1() != null;
        log.debug("Discount by membership {}, user membership discount {}", membershipDiscount, useMembershipDiscount);
        if (useMembershipDiscount) {
            discount += membershipDiscount.getO2();
        }
        if (useMembershipDiscount && useCouponDiscount && couponDiscount.getO1().getCoupon().getMutualExclusionWithMembershipDiscount()) {
            throw new BadRequestException("该优惠券无法和会员卡同时使用");
        }

        log.debug("Total fee = {}, discount = {}", fee, discount);
        try {
            PrePayResultVo<SaoBeiPayResult> res
                    = transactionLogService.preferentialPay(
                    user, shop, totalFee, noDiscountAmount,
                    couponDiscount.getO1(), couponDiscount.getO2() == null ? 0 : couponDiscount.getO2(),
                    membershipDiscount.getO1(), membershipDiscount.getO2() == null ? 0 : membershipDiscount.getO2());
            userAccessShopLogService.log(user, shop);
            return GeneralResult.ok(res);
        } catch (Exception e) {
            e.printStackTrace();
            return GeneralResult.error(e.getMessage());
        }
    }

    /**
     * 刷新交易状态
     *
     * @param tradeNo 交易号
     * @return GeneralResult
     */
    @ApiOperation("刷新交易状态")
    @PostMapping("/{tradeNo}/_refresh")
    public GeneralResult<Map<String, Object>> refreshTradeStatus(@PathVariable("tradeNo") String tradeNo) {
        TransactionLog log = transactionLogService.findByOutTradeNo(tradeNo);
        if (log == null) {
            return GeneralResult.notFound();
        }
        try {
            Map<String, Object> res = new HashMap<>(3);
            Shop shop = shopService.getRequired(log.getShopId());
            TransactionLog transactionLog = transactionLogService.refreshStatus(log, shop, onPaymentSuccessListener, res);
            res.put("transactionLog", transactionLog);
            return GeneralResult.ok(res);
        } catch (Exception e) {
            e.printStackTrace();
            return GeneralResult.error(e.getMessage());
        }
    }

    /**
     * 退款申请
     *
     * @param tradeNo 交易号
     * @return GR
     * @throws IOException IO异常
     */
    @ApiOperation("退款请求")
    @PostMapping("/{tradeNo}/refund")
    public GeneralResult<TransactionLog> refund(@PathVariable("tradeNo") String tradeNo) throws IOException {
        TransactionLog log = transactionLogService.findByTradeNo(tradeNo);
        if (log == null) {
            return GeneralResult.badRequest("交易记录不存在");
        }
        Shop shop = shopService.getRequired(log.getShopId());
        TransactionLog res = transactionLogService.refund(log, shop);
        return GeneralResult.ok(res);
    }

    /**
     * 扫呗交易通知接口
     *
     * @param jsonContent jsonContent
     * @return Map
     */
    @PostMapping("/notify")
    @ApiOperation("扫呗交易结果通知")
    public Map<String, Object> notify(@RequestBody String jsonContent) {
        log.debug("Saobei notify data [{}]", jsonContent);
        SaoBeiNotifyVo notifyVo = JSON.parseObject(jsonContent, SaoBeiNotifyVo.class);
        notifyVo.setSourceJson(jsonContent);
        return transactionLogService.onNotify(notifyVo);
    }
}
